use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Hello, world!");
    // 变量不可变
    let max_number = 100;
    // 变量可变
    let mut number = 3;

    // 代码块, 变量的作用域
    {
        let number = 4;
        println!("{number}");
    }

    println!("{}", max_number);
    println!("{}", number);

    // 字符串
    let str = "hello world";
    println!("{}", str.len());

    // 静态变量, 生命周期是整个程序,全局变量
    static MAX_NUMBER: i32 = 100;
    println!("{MAX_NUMBER}");

    // 常量
    const NUM: i32 = 200 * 200 * 200;
    println!("{}", NUM);

    // 比较猜测的数字和随机数大小

    // 循环猜测

    // 随机数字（1-100）

    /*
    rand::thread_rng 函数提供实际使用的随
    机数生成器：它位于当前执行线程的本地环境中，并从操作系统获取 seed。接着调用随机数
    生成器的 gen_range 方法。这个方法由 use rand::Rng 语句引入到作用域的 Rng trait 定义。
    gen_range 方法获取一个范围表达式（range expression）作为参数，并生成一个在此范围之
    间的随机数。这里使用的这类范围表达式使用了 start..=end 这样的形式，也就是说包含了上
    下端点，所以需要指定 1..=100 来请求一个 1 和 100 之间的数。
     */

    let ran_number = rand::thread_rng().gen_range(1..=100);
    println!("随机数为：{ran_number}");

    loop {
        // 随机数
        println!("猜测数字");
        println!("请输入你要猜测的数字");

        /*
        ::new 那一行的 :: 语法表明 new 是 String 类型的一个 关联函数（associated function）。关联
        函数是针对某个类型实现的函数，在这个例子中是 String。这个 new 函数创建了一个新的空字
        符串。你会发现许多类型上都有一个 new 函数，因为这是为某种类型创建新值的常用函数名。
        总的来说，let mut guess = String::new(); 这一行创建了一个可变变量，当前它绑定到一个
        新的 String 空实例上。
         */
        let mut guess = String::new();

        /*
        & 表示这个参数是一个 引用（reference），它允许多处代码访问同一处数据，而无需在内存中
        多次拷贝。引用是一个复杂的特性，Rust 的一个主要优势就是安全而简单的操纵引用。完成
        当前程序并不需要了解如此多细节。现在，我们只需知道它像变量一样，默认是不可变的。因
        此，需要写成 &mut guess 来使其可变，而不是 &guess。
         */
        io::stdin().read_line(&mut guess).expect("读取失败");

        /*
        我们将 expect 调用换成 match 语句，以从遇到错误就崩溃转换为处理错误。须知 parse 返回
        一个 Result 类型，而 Result 是一个拥有 Ok 或 Err 成员的枚举。这里使用的 match 表达式，和
        之前处理 cmp 方法返回 Ordering 时用的一样。
        如果 parse 能够成功的将字符串转换为一个数字，它会返回一个包含结果数字的 Ok。这个 Ok
        值与 match 第一个分支的模式相匹配，该分支对应的动作返回 Ok 值中的数字 num，最后如愿变
        成新创建的 guess 变量。
        如果 parse 不能将字符串转换为一个数字，它会返回一个包含更多错误信息的 Err。Err 值不
        能匹配第一个 match 分支的 Ok(num) 模式，但是会匹配第二个分支的 Err(_) 模式：_ 是一个通
        配符值，本例中用来匹配所有 Err 值，不管其中有何种信息。所以程序会执行第二个分支的动
        作，continue 意味着进入 loop 的下一次循环，请求另一个猜测。这样程序就有效的忽略了
        parse 可能遇到的所有错误！
         */
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        /*
        这里创建了一个叫做 guess 的变量。不过等等，不是已经有了一个叫做 guess 的变量了吗？确
        实如此，不过 Rust 允许用一个新值来 隐藏 （Shadowing） guess 之前的值。这个功能常用在
        需要转换值类型之类的场景。它允许我们复用 guess 变量的名字，而不是被迫创建两个不同变
        量，诸如 guess_str 和 guess 之类。
         */
        // let guess: u32 = guess
        //     .trim()
        //     .parse()
        //     .expect("请输入一个数字！");

        println!("你猜测的数字是:{}", guess);

        match guess.cmp(&ran_number) {
            Ordering::Less => println! {"猜小了"},
            Ordering::Greater => println! {"猜大了"},
            Ordering::Equal => {
                println!("猜对了!");
                break;
            }
        };
    }

    println!("分割线------------------------------------");

    let x = 5;
    let x = x + 1;
    {
        let x = x * 2;
        println!("内部作用域中的x值为： {x}"); // 12
    }
    println!("x的值是： {x}"); // 6

    println!("分割线------------------------------------");

    let c = 'z';
    let z: char = 'ℤ'; // 带有显式类型注释
    let heart_eyed_cat = '😻';

    // 元组类型

    // 元组是一个将多个其他类型的值组合进一个复合类型的主要方式。
    // 元组长度固定：一旦声明，其长度不会增大或缩小。
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    /*
    tup 变量绑定到整个元组上，因为元组是一个单独的复合元素。为了从元组中获取单个值，可
    以使用模式匹配（pattern matching）来解构（destructure）元组值，像这样：
     */
    let tup = (500, 6.4, 1);
    let (x, y, z) = tup;
    println!("The value of y is: {y}");

    // 我们也可以使用点号（.）后跟值的索引来直接访问它们。例如：
    let x1: (i32, f64, u8) = (500, 6.4, 1);
    let five_hundred = x1.0;
    let six_point_four = x1.1;
    let one = x1.2;

    // 数组类型
    /*
    包含多个值的方式是 数组（array）。与元组不同，数组中的每个元素的类型必须相同。
    Rust 中的数组与一些其他语言中的数组不同，Rust 中的数组长度是固定的。
    我们将数组的值写成在方括号内，用逗号分隔：
     */
    let a1 = [1, 2, 3, 4, 5];
    let months = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    ];

    // 在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组：
    let a2: [i32; 5] = [3; 5];
    /*
    变量名为 a 的数组将包含 5 个元素，这些元素的值最初都将被设置为 3。这种写法与
    let a3 =[3, 3, 3, 3, 3]; 效果相同，但更简洁。
     */
    let a3 = [3, 3, 3, 3, 3];

    // 访问数组元素
    // 数组是可以在栈 (stack) 上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素，像这样：
    let a4 = [1, 2, 3, 4, 5];
    let first = a4[0];
    println!("The first element is: {}", first);

    print_labeled_measurement(5, 'h');
    println!("{}", five());
    println!("{}", plus_one(5));

    compare_sizes();
    compare_sizes2();

    // if 条件语句
    let condition = true;
    let number = if condition { 5 } else { 6 };
    println!("The value of number is: {number}");

    // Rust 有三种循环：loop、while 和 for。
    // loop 循环
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;
        }
    };
    println!("The result is {result}");
}

fn compare_sizes2() {
    let number = 6;
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

// 比较大小
fn compare_sizes() {
    let number1 = 100;
    let number2 = 200;
    if number1 > number2 {
        println!("{}>{}", number1, number2);
    } else {
        println!("{}<{}", number1, number2);
    }
}

// 在函数签名中，必须 声明每个参数的类型。
fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

// 函数可以向调用它的代码返回值。我们并不对返回值命名，但要在箭头（->）后声明它的类型。
fn five() -> i32 {
    5 // 或者return 5;
}

// x+1没有分号，所以是表达式，而不是语句。
fn plus_one(x: i32) -> i32 {
    x + 1
}
